package core;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.TreeMultimap;

import jdd.zdd.ZDD;

import search.FileIndexBuilder;
import search.FileIndexBuilderBasic;
import search.SimpleSearchRanker;
import util.DocUtils;
import util.FileFinder;

public class NNZDD extends Core {
	// ZDD
	ZDD _zdd;
	int _zddRoot;
	BiMap<String, Integer> _stemVarDict;
	BiMap<Integer, String> _stemVarInvDict;
	HashMap<Integer, Boolean> _nodeMaxBranchDict;
	final int _zddBaseCost = 1;
	final int _zddEmptyCost = -2; 
	
	// Lucene
	String _indexPath = "lucene.index";
	String default_field = "CONTENT";
	FileIndexBuilderBasic _fileIndexBuilder;
	SimpleSearchRanker _luceneSearch;

	
	
	public NNZDD(String trainDataDir) {
		super(trainDataDir);
		_nnDescShort = "NN-ZDD-HD-LUC";
		_nnDescLong = "Nearest Neighbor with ZDD Hamming Distance (Using Lucene to retrieve doc)";
	}
	
	public void init() {
		_zdd = new ZDD(5000000, 100000);
		_stemVarDict = HashBiMap.create();
		_stemVarInvDict = _stemVarDict.inverse();
		_nodeMaxBranchDict = new HashMap<Integer, Boolean>();
	}
	
	protected int changeWith(int zddSet, int wordVar) {
		int tmp = _zdd.ref(_zdd.change(zddSet, wordVar));
		_zdd.deref(zddSet);
		return tmp;
	}

	protected int unionWith(int zddSetA, int zddSetB) {
		int tmp = _zdd.ref(_zdd.union(zddSetA, zddSetB));
		_zdd.deref(zddSetA);
		return tmp;
	}
	
	// Convert topic to ZDD
	public int topic2Zdd(File file) {
		String fileContent = DocUtils.ReadFile(file);
		Map<Object, Double> wordCount = DocUtils.ConvertToFeatureMapFilter(fileContent);
		int zddTopic = _zdd.base();

		for (Map.Entry<Object, Double> me : wordCount.entrySet()) {
			// If the stem word have not been created as variable
			if (!_stemVarDict.containsKey(me.getKey())) {
				int zddVar = _zdd.createVar();
				_stemVarDict.put((String) me.getKey(), zddVar);
			}
			zddTopic = changeWith(zddTopic, _stemVarDict.get(me.getKey()));
		}
		return zddTopic;
	}

	// Union all the topics' ZDD
	protected int zddTopicUnion() {
		ArrayList<File> files = FileFinder.GetAllFiles( _trainDataDir, "", true);
		int zddTopic;
		int zddTmp = _zdd.base();
		int numFileProcessed = 0;

		for (File f : files) {
			zddTopic = topic2Zdd(f);
			zddTmp = unionWith(zddTmp, zddTopic);
			numFileProcessed++;
			
			if (numFileProcessed%1000 == 0) {
				System.out.println("File Processed: " + numFileProcessed);
			}
		}

		return zddTmp;
	}
	

	protected double reduceScoreMain(int zdd, String targetFilename) {
		File targetFile = new File(targetFilename);
		Map<Object, Double> targetWordCount = DocUtils.ConvertToFeatureMapFilter(DocUtils.ReadFile(targetFile));
		_nodeMaxBranchDict = new HashMap<Integer, Boolean>();
		return reduceScore(zdd, targetWordCount);
	}

	public double reduceScore(int zdd, Map<Object, Double> targetWordCount) {
		// Empty Set
		if (zdd == 0)
			return _zddEmptyCost;
		// Base Set
		if (zdd == 1)
			return _zddBaseCost;

		int zddLow = _zdd.subset0(zdd, _zdd.getVar(zdd));
		int zddHigh = _zdd.subset1(zdd, _zdd.getVar(zdd));
		
		// Calculate hamming distance
		int zddContain = (targetWordCount.containsKey(_stemVarInvDict.get(_zdd.getVar(zdd)))) ? 1 : 0;

		double zddHighCost = reduceScore(zddHigh, targetWordCount) + zddContain;
		double zddLowCost = reduceScore(zddLow, targetWordCount);

		double zddCost = 0;
		boolean maxBranch;

		if (zddHighCost > zddLowCost) {
			zddCost = zddHighCost;
			maxBranch = true;
		} else {
			zddCost = zddLowCost;
			maxBranch = false;
		}
		_nodeMaxBranchDict.put(zdd, maxBranch);

		return zddCost;
	}
	
	public void extractSet(int zdd, Set<String> m) {
		int zddMaxBranch;

		if (!(zdd == 0 || zdd == 1)) {
			if (_nodeMaxBranchDict.get(zdd)) {
				String keyWord = _stemVarInvDict.get(_zdd.getVar(zdd));
				m.add(keyWord);
				zddMaxBranch = _zdd.subset1(zdd, _zdd.getVar(zdd));
			} else {
				zddMaxBranch = _zdd.subset0(zdd, _zdd.getVar(zdd));
			}
			extractSet(zddMaxBranch, m);
		}
	}
	
	protected Set<String> extractSet() {
		Set<String> setExtract = new HashSet<String>();
		extractSet(_zddRoot, setExtract);
		return setExtract;
	}
	
	protected String set2String(Set<String> m) {
		String result = "";
		for (String e : m) {
			result = result + " " + e;
		}
		
		return result.trim();
	}

	public void build() {
		try {
			ArrayList<File> files = FileFinder.GetAllFiles( _trainDataDir, "", true);
			// ZDD Variable
			int zddTopic;
			int zddTmp = _zdd.base();
			
			_fileIndexBuilder = new FileIndexBuilderBasic(_indexPath);
			int numFileProcessed = 0;
			
			for (File f : files) {
				
			}
		} catch (IOException e) {
			e.printStackTrace(System.err);
		} finally {
			_fileIndexBuilder.close();
		}

//		// Build ZDD Index
//		_zddRoot = zddTopicUnion();
//		// Build Lucene Index
//		try {
//			_fileIndexBuilder = new FileIndexBuilder(_indexPath);
//			_fileIndexBuilder.addFiles(FileFinder.GetAllFiles( _trainDataDir, "", true), 
//					true /*clear_old_index = false if adding*/);
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}

	}
	
	public String query(String targetDataName) {
		double sim = reduceScoreMain(_zddRoot, targetDataName);
		Set<String> setExtract = new HashSet<String>();
		extractSet(_zddRoot, setExtract);
		
		if (DEBUG) {
			System.out.println(set2String(setExtract));
		}
		
		try {
			_luceneSearch = new SimpleSearchRanker(_indexPath, default_field, _fileIndexBuilder._analyzer);
			try {
//				_luceneSearch.doSearch(set2String(setExtract), 1, System.out);
				return _luceneSearch.doSearch(set2String(setExtract));
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return "";
	}
	
	public int size() {
		return _zdd.nodeCount(_zddRoot);
	}
	
	public void clear() {
		_zdd.cleanup();
		_stemVarDict.clear();
		_stemVarInvDict.clear();
		_nodeMaxBranchDict.clear();

		// Lucene
		_fileIndexBuilder = null;
		_luceneSearch = null;
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String trainDataDir = "data/more_newsgroups/alt.atheism";
		String targetFile = trainDataDir + "/53141";
		NNZDDBasic nnZDD = new NNZDDBasic(trainDataDir);
		nnZDD.init();
		nnZDD.build();
		nnZDD.query(targetFile);
		System.out.println(nnZDD.set2String(nnZDD.extractSet()));
		

	}

}
